<?php
/**
 * 阿里云开放存储服务（OSS）PHP SDK开发包
 *
 * @author zendzhang<zendzhang@hotmail.com>
 * @date 2013-01-04
 * @version $Id: oss_sdk.class.php 3 2013-01-04 10:21:37Z zendzhang@hotmail.com $
 */
class OSS_SDK
{
    const VERSION = '1.0.0';

    /**
     * Request对象实例
     *
     * @var object
     */
    private static $request = null;

    /**
     * 构造函数
     *
     * @param string $accessID 用户ID
     * @param string $accessKey 用户密钥
     */
    function __construct($accessID, $accessKey)
    {
        self::$request = new OSS_Request($accessID, $accessKey);
    }

    /**
     * 魔术方法, 动态实例化一个Bucket对象
     *
     * @param string $bucket Bucket名称
     * @return OSS_Bucket
     */
    public function __get($bucket)
    {
        return $this->$bucket = new OSS_Bucket($bucket, self::$request);
    }

    /**
     * 自定义配置信息
     *
     * @param mixed $configs 配置名或名-值对
     * @param mixed $value	配置值
     * @return void
     */
    public function setConfig($configs, $value = null)
    {
        if (!is_array($configs))
        {
            $configs = array($configs => $value);
        }
        foreach ($configs as $property => $value)
        {
            self::$request->$property = $value;
        }
    }

    ///////////////////////////// Bucket相关操作 ///////////////////////////////////
    /**
     * 获取所有Bucket
     *
     * @access public
     * @return OSS_Response
     */
    public function listBuckets()
    {
        return self::$request->get('/');
    }

    /**
     * 创建Bucket，最多可创建10个Bucket(命名空间)
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $acl Bucket的访问权限，默认为'private'
     * @return OSS_Response
     */
    public function createBucket($bucket, $acl = 'private')
    {
        return $this->$bucket->create($acl);
    }

    /**
     * 删除Bucket
     *
     * @access public
     * @param string $bucket Bucket名称
     * @return OSS_Response
     */
    public function deleteBucket($bucket)
    {
        return $this->$bucket->delete();
    }

    /**
     * 获取Bucket的访问权限
     *
     * @access public
     * @param string $bucket Bucket名称
     * @return OSS_Response
     */
    public function getBucketACL($bucket)
    {
        return $this->$bucket->acl();
    }

    /**
     * 设置Bucket的访问权限
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $acl Bucket的访问权限
     * @return OSS_Response
     */
    public function setBucketACL($bucket, $acl)
    {
        return $this->$bucket->acl($acl);
    }

    /**
     * 根据筛选条件获取Bucket中的Object列表
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param array $params 请求参数,用于设定返回的内容
     * - prefix     设定仅返回前缀为prefix的Object（包含目录）
     * - max-keys   设定返回的Object最大数，默认为1000
     * - marker     设定结果从marker之后按字母排序的第一个开始返回
     * - delimiter  设定一个用于对Object名字进行分组的字符
     * @return OSS_Response
     */
    public function listObjects($bucket, array $params = array())
    {
        return $this->$bucket->objects($params);
    }

    /**
     * 删除同个Bucket下的多个Object
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param array $objects 待删除的Object名称列表
     * @param bool $quiet 是否使用简单模式返回删除结果，默认为详细模式verbose
     * @return OSS_Response
     */
    public function deleteObjects($bucket, array $objects, $quiet = false)
    {
        return $this->$bucket->deletes($objects, $quiet);
    }

    /**
     * 根据筛选条件获取所有执行中的 UploadMultipart Upload事件
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param array $params 请求参数,用于设定返回的内容
     * - prefix     设定返回的 object key必须以prefix作为前缀
     * - max-uploads 设定返回的事件最大数，默认为1000
     * - delimiter  设定一个用于对Object名字进行分组的字符
     * - key-marker 与upload-id-marker参数一同使用来指定返回结果的起始位置
     * - upload-id-marker 与key-marker参数一同使用来指定返回结果的起始位置
     * @return OSS_Response
     */
    public function listMultis($bucket, array $params = array())
    {
        return $this->$bucket->multis($params);
    }

    /**
     * 通过Multipart Upload上传模式上传指定目录中的所有文件
     *
     * @param string $bucket Bucket名称
     * @param string $dir 目录名
     * @param boolean $recursive 是否递归遍历子目录
     * @param string $exclude 过滤的文件/目录
     * @return array
     */
    public function multiLazyUploadByDir($bucket, $dir, $recursive = false, $exclude = ".|..|.svn")
    {
        return $this->$bucket->multiLazyUploadByDir($dir, $recursive, $exclude);
    }

    // /////////////////////////// Object相关操作 //////////////////////////////
    /**
     * 以Object的名称创建一个目录
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @access public
     * @return OSS_Response
     */
    public function mkdir($bucket, $object)
    {
        return $this->$bucket->object($object)->mkdir();
    }

    /**
     * 根据文件数据内容创建Object
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $content 文件数据内容
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function createObjectByContent($bucket, $object, $content, array $headers = array())
    {
        return $this->$bucket->object($object)->content($content, $headers);
    }

    /**
     * 根据指定文件创建Object
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $filename 完整的文件路径
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function createObjectByFile($bucket, $object, $filename, array $headers = array())
    {
        return $this->$bucket->object($object)->upload($filename, $headers);
    }

    /**
     * 获取Object
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $saveAs 下载存放路径
     * @param array $headers 请求头信息
     * @param array $params 请求参数
     * @return OSS_Response
     */
    public function getObject($bucket, $object, $saveAs = null, array $headers = array(), array $params = array())
    {
        return $this->$bucket->object($object)->get($saveAs, $headers, $params);
    }

    /**
     * 拷贝Object
     *
     * @access public
     * @param string $souceBucket 源Bucket名称
     * @param string $souceObject 源Object名称
     * @param string $destBucket 目标Bucket名称
     * @param string $destObject 目标Object名称
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function copyObject($souceBucket, $souceObject, $destBucket, $destObject, array $headers = array())
    {
        return $this->$souceBucket->object($souceObject)->copy($destBucket, $destObject, $headers);
    }

    /**
     * 获取Object的META信息
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param array $headers 请求Header
     * @return OSS_Response
     */
    public function getObjectMeta($bucket, $object, array $headers = array())
    {
        return $this->$bucket->object($object)->meta($headers);
    }

    /**
     * 删除单个Object
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @return OSS_Response
     */
    public function deleteObject($bucket, $object)
    {
        return $this->$bucket->object($object)->delete();
    }

    /**
     * 检测Object是否存在
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @return OSS_Response
     */
    public function isObjectExist($bucket, $object)
    {
        return $this->$bucket->object($object)->exist();
    }

    /**
     * 构造一个带签名的Object URL
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param int $expire 有效时间（秒）
     * @return string
     */
    public function objectURL($bucket, $object, $expire = 60)
    {
        return $this->$bucket->object($object)->url($expire);
    }

    ///////////////////// UploadMultipart Upload 相关操作  ///////////////////////
    /**
     * 初始 化一个Multipart Upload事件
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function multiInitiate($bucket, $object, array $headers = array())
    {
        return $this->$bucket->multi($object)->initiate($headers);
    }

    /**
     * 上传Part（分块）数据
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $uploadID Upload事件ID
     * @param int $number Part编号(1~10000)
     * @param array $streams 流数据自定义信息[stream,length|seek]
     * @return OSS_Response
     */
    public function multiUploadPart($bucket, $object, $uploadID, $number, $streams)
    {
        return $this->$bucket->multi($object)->uploadPart($uploadID, $number, $streams);
    }

    /**
     * 完成Multipart Upload事件
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $uploadID Upload事件ID
     * @param array $parts Part标识列表,array(array('PartNumber'=>1,'ETag'=>'"XX"'))
     * @return OSS_Response
     */
    public function multiComplete($bucket, $object, $uploadID, $parts)
    {
        return $this->$bucket->multi($object)->complete($uploadID, $parts);
    }

    /**
     * 中断Multipart Upload事件
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $uploadID Upload事件ID
     * @return OSS_Response
     */
    public function multiAbort($bucket, $object, $uploadID)
    {
        return $this->$bucket->multi($object)->abort($uploadID);
    }

    /**
     * 获取所指定的UploadID上传成功的Part，不推荐用listParts出来的结果来生成Complete方法的Part列表
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param string $uploadID Upload事件ID
     * @param int $maxParts 最大返回的Part数，默认为1000
     * @param string $marker 设定结果从marker之后按字母排序的第一个开始返回
     * @return OSS_Response
     */
    public function multiListParts($bucket, $object, $uploadID, $maxParts = 1000, $marker = '')
    {
        return $this->$bucket->multi($object)->listParts($uploadID, $maxParts, $marker);
    }

    /**
     * Multipart Upload上传模式的封装实现
     *
     * @param string $bucket Bucket名称
     * @param string $object Object名称
     * @param array $streams 流数据自定义信息[stream,length|seek]
     * @param int $partSize 默认分块大小，50M
     * @param string $uploadID Upload事件ID
     * @return OSS_Response
     */
    public function multiLazyUpload($bucket, $object, array $streams, $partSize = 52428800, $uploadID = null)
    {
        return $this->$bucket->multi($object)->lazyUpload($streams, $partSize, $uploadID);
    }

    ////////////////////////////// Object Group相关操作 //////////////////////////////
    /**
     * 为指定的多个Object创建一个Group
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $group Group名称
     * @param string $objects Object名称列表
     * @return OSS_Response
     */
    public function createObjectGroup($bucket, $group, array $objects)
    {
        return $this->$bucket->group($group)->create($objects);
    }

    /**
     * 获取单个Group，所使用的请求参数和请求头信息与“获取Object”的参数一致
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $group Group名称
     * @param array $headers 请求头信息
     * @param array $params 请求参数
     * @return OSS_Response
     */
    public function getObjectGroup($bucket, $group, array $headers = array(), array $params = array())
    {
        return $this->$bucket->group($group)->get($headers, $params);
    }

    /**
     * 获取Group中的Object列表信息
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $group Group名称
     * @return OSS_Response
     */
    public function getObjectGroupIndex($bucket, $group)
    {
        return $this->$bucket->group($group)->index();
    }

    /**
     * 获取Group中的META信息，所使用的请求头信息与“获取Object Meta”的参数一致
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $group Group名称
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function getObjectGroupMeta($bucket, $group, array $headers = array())
    {
        return $this->$bucket->group($group)->meta($headers);
    }

    /**
     * 删除Group
     *
     * @access public
     * @param string $bucket Bucket名称
     * @param string $group Group名称
     * @return OSS_Response
     */
    public function deleteObjectGroup($bucket, $group)
    {
        return $this->$bucket->group($group)->delete();
    }
}

/************************************** SDK内部实现类 ****************************************/
/*
|--------------------------------------------------------------------
| Bucket操作类
|--------------------------------------------------------------------
|
| 包括Bucket创建、删除、设置权限，以及Object的批量操作和获取执行中的Multipart上传事件
|
*/
class OSS_Bucket
{
    /**
     * Bucket名称（OSS命名空间）
     *
     * @var string
     */
    private $bucket;

    /**
     * Request对象实例
     *
     * @var object
     */
    private $request = null;

    /**
     * 构造函数
     *
     * @param string $bucket Bucket名称
     * @param object $request Request对象实例
     * @throws OSS_Exception
     */
    public function __construct($bucket, &$request)
    {
        OSS_UT::parseBucket($bucket);

        $this->bucket = $bucket;
        $this->request = $request;
    }

    /**
     * 创建Bucket，最多可创建10个Bucket(命名空间)
     *
     * @access public
     * @param string $acl Bucket的访问权限，默认为'private'
     * @throws OSS_Exception
     * @return OSS_Response
     */
    public function create($acl = 'private')
    {
        if (!in_array(strtolower($acl), array('private', 'public-read', 'public-read-write')))
        {
            throw new OSS_Exception('Invalid ACL: ' . $acl);
        }
        return $this->request->put($this->bucket, array('x-oss-acl' => $acl));
    }

    /**
     * 删除Bucket
     *
     * @access public
     * @return OSS_Response
     */
    public function delete()
    {
        return $this->request->delete($this->bucket);
    }

    /**
     * 设置或获取Bucket的访问权限(ACL)
     *
     * @access public
     * @param string $acl acl为null时表示获取Bucket的ACL，反之为设置ACL
     * @return OSS_Response
     */
    public function acl($acl = null)
    {
        return null === $acl ? $this->request->get("$this->bucket?acl") : $this->create($acl);
    }

    /**
     * 根据筛选条件获取Bucket中的Object列表
     *
     * @access public
     * @param array $params 请求参数,用于设定返回的内容
     * - prefix     设定仅返回前缀为prefix的Object（包含目录）
     * - max-keys   设定返回的Object最大数，默认为1000
     * - marker     设定结果从marker之后按字母排序的第一个开始返回
     * - delimiter  设定一个用于对Object名字进行分组的字符
     * @return OSS_Response
     */
    public function objects(array $params = array())
    {
        $params = array_merge(array
        (
            'prefix' => '',
            'max-keys' => 1000,
            'marker' => '',
            'delimiter' => '/'

        ), $params);
        return $this->request->get($this->bucket . '?' . http_build_query($params));
    }

    /**
     * 删除同个Bucket下的多个Object
     *
     * @access public
     * @param array $objects 待删除的Object名称列表
     * @param bool $quiet 是否使用简单模式返回删除结果，默认为详细模式verbose
     * @return OSS_Response
     */
    public function deletes(array $objects, $quiet = false)
    {
        if (empty($objects))
        {
            throw new OSS_Exception('The `objects` option is empty');
        }
        $xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><Delete></Delete>');
        $xml->addChild('Quiet', $quiet ? 'true' : 'false');
        foreach ($objects as $object)
        {
            $xml->addChild('Object')->addChild('Key', htmlentities($object, ENT_QUOTES));
        }
        $headers = array(
            'Content-Type' => 'application/xml'
        );
        return $this->request->post("$this->bucket?delete", $headers, $xml->asXML());
    }

    /**
     * 根据筛选条件获取所有执行中的 UploadMultipart Upload事件
     *
     * @access public
     * @param array $params 请求参数,用于设定返回的内容
     * - prefix     设定返回的 object key必须以prefix作为前缀
     * - max-uploads 设定返回的事件最大数，默认为1000
     * - delimiter  设定一个用于对Object名字进行分组的字符
     * - key-marker 与upload-id-marker参数一同使用来指定返回结果的起始位置
     * - upload-id-marker 与key-marker参数一同使用来指定返回结果的起始位置
     * @return OSS_Response
     */
    public function multis(array $params = array())
    {
        $params = array_merge(array
        (
            'prefix' => '',
            'max-uploads' => 1000,
            'delimiter' => '/'

        ), $params);
        return $this->request->get($this->bucket . '?uploads&' . http_build_query($params));
    }

    /**
     * 通过Multipart Upload上传模式上传指定目录中的所有文件
     *
     * @param string $dir 目录名
     * @param boolean $recursive 是否递归遍历子目录
     * @param string $exclude 过滤的文件/目录
     * @return array
     */
    public function multiLazyUploadByDir($dir, $recursive = false, $exclude = ".|..|.svn")
    {
        $dir = OSS_UT::tryUTF2GBKforCN($dir);

        if (!is_dir($dir))
        {
            throw new OSS_Exception($dir . ' is not a directory, pls check it');
        }
        $files = OSS_UT::readDir($dir, $exclude, $recursive);
        if (empty($files))
        {
            throw new OSS_Exception($dir . ' is empty');
        }

        $result = array('succ' => array(), 'fail' => array());
        foreach ($files as $file)
        {
            $object = substr($file['path'], strlen($dir) + 1);
            $streams = array('stream' => $file['path']);
            $response = $this->multi($object)->lazyUpload($streams, 5242880);
            if ($response->isOK())
            {
                $result['succ'][] = $object;
            }
            else
            {
                $result['fail'][] = $object;
            }
        }
        return $result;
    }

    /**
     * 获取一个Object实例
     *
     * @access public
     * @param string $object Object名称
     * @return OSS_Object
     */
    public function object($object)
    {
        return new OSS_Object($object, $this->bucket, $this->request);
    }

    /**
     * 获取一个Multi实例
     *
     * @access public
     * @param string $object Object名称
     * @return OSS_Multi
     */
    public function multi($object)
    {
        return new OSS_Multi($object, $this->bucket, $this->request);
    }

    /**
     * 获取一个Object Group实例
     *
     * @access public
     * @param string $group Group名称
     * @return OSS_Group
     */
    public function group($group)
    {
        return new OSS_Group($group, $this->bucket, $this->request);
    }
}

/*
|--------------------------------------------------------------------
| Object操作类
|--------------------------------------------------------------------
|
| 包括目录创建、Object创建、读取、拷贝、获取META信息、删除操作
|
*/
class OSS_Object
{
    /**
     * Object名称（文件名）
     *
     * @var string
     */
    protected $object;

    /**
     * 所属的Bucket名称
     *
     * @var string
     */
    protected $bucket;

    /**
     * Request对象实例
     *
     * @var object
     */
    protected $request = null;

    /**
     * 构造函数
     *
     * @param string $object Object名称
     * @param string $bucket Bucket名称
     * @param object $request Request对象实例
     * @throws OSS_Exception
     */
    public function __construct($object, $bucket, &$request)
    {
        OSS_UT::parseObject($object);

        $this->object = $object;
        $this->bucket = $bucket;
        $this->request = $request;
    }

    /**
     * 以Object的名称创建一个目录
     *
     * @access public
     * @return OSS_Response
     */
    public function mkdir()
    {
        $headers = array('Content-Length' => 0);

        return $this->request->put("$this->bucket/$this->object/", $headers);
    }

    /**
     * 根据文件数据内容创建Object
     *
     * @access public
     * @param string $content 文件数据内容
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function content($content, array $headers = array())
    {
        if (!is_string($content) || '' === trim($content))
        {
            throw new OSS_Exception('Invalid object content');
        }
        $headers['Content-Type'] = OSS_UT::mimeType($this->object);
        return $this->request->put("$this->bucket/$this->object", $headers, $content);
    }

    /**
     * 根据指定文件创建Object
     *
     * @access public
     * @param string $filename 完整的文件路径
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function upload($filename, array $headers = array())
    {
        $filename = OSS_UT::tryUTF2GBKforCN($filename);
        if (!is_file($filename))
        {
            throw new OSS_Exception('File not exists: ' . $filename);
        }
        $headers['Content-Type'] = OSS_UT::mimeType($this->object);
        $streams = array('stream' => $filename);
        return $this->request->put("$this->bucket/$this->object", $headers, null, $streams);
    }

    /**
     * 获取Object
     *
     * @access public
     * @param string $saveAs 下载存放路径
     * @param array $headers 请求头信息
     * @param array $params 请求参数
     * @return OSS_Response
     */
    public function get($saveAs = null, array $headers = array(), array $params = array())
    {
        $query = empty($params) ? '' : '?' . http_build_query($params);
        $streams = array();
        if (null !== $saveAs)
        {
            $streams['stream'] = OSS_UT::tryUTF2GBKforCN($saveAs);
        }
        return $this->request->get("$this->bucket/$this->object$query", $headers, null, $streams);
    }

    /**
     * 拷贝Object
     *
     * @access public
     * @param array $destBucket 目标Bucket名称
     * @param array $destObject 目标Object名称
     * @param array $headers 请求头信息
     * @throws OSS_Exception
     * @return OSS_Response
     */
    public function copy($destBucket, $destObject, array $headers = array())
    {
        OSS_UT::parseBucket($destBucket);
        OSS_UT::parseObject($destObject);

        $headers['x-oss-copy-source'] = "/$this->bucket/$this->object";
        return $this->request->put("$destBucket/$destObject", $headers);
    }

    /**
     * 获取Object的META信息
     *
     * @access public
     * @param array $headers 请求Header
     * @return OSS_Response
     */
    public function meta(array $headers = array())
    {
        return $this->request->head("$this->bucket/$this->object", $headers);
    }

    /**
     * 删除单个Object
     *
     * @access public
     * @return OSS_Response
     */
    public function delete()
    {
        return $this->request->delete("$this->bucket/$this->object");
    }

    /**
     * 检测Object是否存在
     *
     * @access public
     * @return OSS_Response
     */
    public function exist()
    {
        return $this->meta();
    }

    /**
     * 构造一个带签名的Object URL
     *
     * @access public
     * @param int $expire 有效时间（秒）
     * @return string
     */
    public function url($expire = 60)
    {
        $headers = array
        (
            'Sign-URL'     => '',
            'Content-Type' => '',
            'Date'         => ($expire > 0 ? time() + $expire : 0)
        );
        return $this->request->get("$this->bucket/$this->object", $headers);
    }
}
/*
|--------------------------------------------------------------------
| Multipart Upload的操作类
|--------------------------------------------------------------------
|
| 包括初始化事件、上传Part、完成上传及中断上传事件、获取已上传成功的Part
|
*/
class OSS_Multi extends OSS_Object
{

    /**
     * 构造函数
     *
     * @param string $object Object名称
     * @param string $bucket Bucket名称
     * @param object $request Request对象实例
     * @throws OSS_Exception
     */
    public function __construct($object, $bucket, &$request)
    {
        parent::__construct($object, $bucket, $request);
    }

    /**
     * 初始 化一个Multipart Upload事件
     *
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function initiate(array $headers = array())
    {
        $headers['Content-Type'] = 'application/octet-stream';

        return $this->request->post("$this->bucket/$this->object?uploads", $headers);
    }

    /**
     * 上传Part（分块）数据
     *
     * @param string $uploadID Upload事件ID
     * @param int $number Part编号(1~10000)
     * @param array $streams Part数据自定义信息[stream,length|seek]
     * @return OSS_Response
     */
    public function uploadPart($uploadID, $number, array $streams)
    {
        $headers['Content-Type'] = 'application/octet-stream';

        return $this->request->put("$this->bucket/$this->object?partNumber=$number&uploadId=$uploadID", $headers, null, $streams);
    }

    /**
     * 完成Multipart Upload事件
     *
     * @param string $uploadID Upload事件ID
     * @param mixed $parts Part标识列表,array(array('PartNumber'=>1,'ETag'=>'"XX"'))
     * @return OSS_Response
     */
    public function complete($uploadID, $parts)
    {
        $content = $parts;
        if (is_array($parts))
        {
            $xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><CompleteMultipartUpload></CompleteMultipartUpload>');
            foreach ($parts as $node)
            {
                $part = $xml->addChild('Part');
                $part->addChild('PartNumber', $node['PartNumber']);
                $part->addChild('ETag', $node['ETag']);
            }
            $content = $xml->asXML();
        }
        $headers = array('Content-Type' => 'application/xml');
        return $this->request->post("$this->bucket/$this->object?uploadId=$uploadID", $headers, $content);
    }

    /**
     * 中断Multipart Upload事件
     *
     * @param string $uploadID Upload事件ID
     * @return OSS_Response
     */
    public function abort($uploadID)
    {
        return $this->request->delete("$this->bucket/$this->object?uploadId=$uploadID");
    }

    /**
     * 获取所指定 的UploadID上传成功的 Part，不推荐用listParts出来的结果来生成Complete方法的Part列表
     *
     * @param string $uploadID Upload事件ID
     * @param int $maxParts 最大返回的Part数，默认为1000
     * @param string $marker 设定结果从marker之后按字母排序的第一个开始返回
     * @return OSS_Response
     */
    public function listParts($uploadID, $maxParts = 1000, $marker = '')
    {
        return $this->request->get("$this->bucket/$this->object?uploadId=$uploadID&max-parts=$maxParts&part-number-marker=$marker");
    }

    /**
     * Multipart Upload上传模式的封装实现
     *
     * @param array $streams 流数据自定义信息[stream,length|seek]
     * @param int $partSize 默认分块大小，50M
     * @param string $uploadID Upload事件ID
     * @return OSS_Response
     */
    public function lazyUpload(array $streams, $partSize = 52428800, $uploadID = null)
    {
        OSS_UT::parsePartSize($partSize);

        $os = new OSS_Stream($streams, true);
        $length = $os->getReadLength();
        if (null === $uploadID)
        {
            // 小于默认分块大小的文件直接使用普通上传方式
            if ($length < $partSize)
            {
                return $this->upload($streams['stream']);
            }

            // 申请uploadID
            $response = $this->initiate();
            if (!$response->isOK())
            {
                throw new OSS_Exception('Initiate multipart upload failed');
            }
            $xml = new SimpleXmlIterator($response->body);
            $uploadID = (string) $xml->UploadId;
        }

        // 分块
        $parts = array();
        $stats = $length;
        $ii = 0;
        while ($stats > 0)
        {
            $stats -= $partSize;
            $parts[] = array
            (
                'seek'    => ($partSize * $ii),
                'length'  => ($stats > 0 ? $partSize : ($stats + $partSize))
            );
            $ii++;
        }

        // 上传各个分块
        $responses = array();
        foreach ($parts as $index => $part)
        {
            $_streams = array
            (
                'stream' => $streams['stream'],
                'length' => $part['length'],
                'seek'   => $os->getReadSeek() + $part['seek']
            );
            $number = $index + 1;
            $response = $this->uploadPart($uploadID, $number, $_streams);
            if (!$response->isOK())
            {
                throw new OSS_Exception("Part $number upload failed, pls try again");
            }
            $responses[] = $response;
        }

        // 组合成完整的Object
        $list = array();
        foreach ($responses as $index => $response)
        {
            $list[] = array
            (
                'PartNumber' => ($index + 1),
                'ETag'       => (string) $response->header['ETag']
            );
        }
        return $this->complete($uploadID, $list);
    }
}
/*
|--------------------------------------------------------------------
| Object Group操作类
|--------------------------------------------------------------------
|
| 包括Group创建、读取、获取Object列表、获取META信息和删除操作
|
*/
class OSS_Group
{
    /**
     * Group名称
     *
     * @var string
     */
    private $group;

    /**
     * 所属的Bucket名称
     *
     * @var string
     */
    private $bucket;

    /**
     * Request对象实例
     *
     * @var object
     */
    private $request = null;

    /**
     * 构造函数
     *
     * @param string $group Group名称
     * @param string $bucket Bucket名称
     * @param object $request Request对象实例
     * @throws OSS_Exception
     */
    public function __construct($group, $bucket, &$request)
    {
        OSS_UT::parseObject($group);

        $this->group = $group;
        $this->bucket = $bucket;
        $this->request = $request;
    }

    /**
     * 为指定的多个Object创建一个Group
     *
     * @access public
     * @param string $objects Object名称列表
     * @return OSS_Response
     */
    public function create(array $objects)
    {
        if (empty($objects))
        {
            throw new OSS_Exception('The `objects` option is empty');
        }
        if (count($objects) > 1000)
        {
            throw new OSS_Exception('Too many objects');
        }
        $xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><CreateFileGroup></CreateFileGroup>');
        foreach ($objects as $index => $object)
        {
            $obj = new OSS_Object($object, $this->bucket, $this->request);
            $meta = (array) $obj->meta();
            if (!empty($meta) && isset($meta['status'], $meta['header'], $meta['header']['ETag']) && 200 == $meta['status'])
            {
                $part = $xml->addChild('Part');
                $part->addChild('PartNumber', intval($index) + 1);
                $part->addChild('PartName', $object);
                $part->addChild('ETag', $meta['header']['ETag']);
            }
        }
        $headers = array('Content-Type' => 'application/xml');
        return $this->request->post("$this->bucket/$this->group?group", $headers, $xml->asXML());
    }

    /**
     * 获取单个Group，所使用的请求参数和请求头信息与“获取Object”的参数一致
     *
     * @access public
     * @param array $headers 请求头信息
     * @param array $params 请求参数
     * @return OSS_Response
     */
    public function get(array $headers = array(), array $params = array())
    {
        $headers['x-oss-file-group'] = '1';
        $query = empty($params) ? '' : '?' . http_build_query($params);

        return $this->request->get("$this->bucket/$this->group$query", $headers);
    }

    /**
     * 获取Group中的Object列表信息
     *
     * @access public
     * @return OSS_Response
     */
    public function index()
    {
        $headers = array('x-oss-file-group' => '1');

        return $this->request->get("$this->bucket/$this->group", $headers);
    }

    /**
     * 获取Group中的META信息，所使用的请求头信息与“获取Object Meta”的参数一致
     *
     * @access public
     * @param array $headers 请求头信息
     * @return OSS_Response
     */
    public function meta(array $headers = array())
    {
        $headers['x-oss-file-group'] = '1';

        return $this->request->head("$this->bucket/$this->group", $headers);
    }

    /**
     * 删除Group
     *
     * @access public
     * @return OSS_Response
     */
    public function delete()
    {
        return $this->request->delete("$this->bucket/$this->group");
    }
}

/*
|--------------------------------------------------------------------
| OSS请求操作类
|--------------------------------------------------------------------
|
*/
class OSS_Request
{
    /**
     * 用户ID、密钥
     *
     * @var string
     */
    private $accessID, $accessKey;

    /**
     * OSS服务主机域名
     *
     * @var string
     */
    private $host = 'oss.aliyuncs.com';

    /**
     * 代理服务器设置[host,port,user,pass]
     *
     * @var array
     */
    private $proxy = array();

    /**
     * 启用SSL验证
     *
     * @var bool
     */
    private $useSSL = false;

    /**
     * 默认连接超时时间（秒）
     *
     * @var int
     */
    private $connectTimeout = 30;

    /**
     * 最大持续执行时间（秒）
     *
     * @var int
     */
    private $timeout = 86400;

    /**
     * 最大重试次数
     *
     * @var int
     */
    private $maxRetries = 3;

    /**
     * 累计重试次数
     *
     * @var int
     */
    private $redirects = 0;

    /**
     * 日志存储目录
     *
     * @var string
     */
    private $logDir = '';

    /**
     * debug模式
     *
     * var bool
     */
    private $debug = false;

    /**
     * 构造函数
     *
     * @param string $accessID 用户ID
     * @param string $accessKey 用户密钥
     */
    function __construct($accessID, $accessKey)
    {
        $this->accessID = $accessID;
        $this->accessKey = $accessKey;
    }

    /**
     * 魔术方法，重置自定义配置信息
     *
     * @param mixed $property 属性名
     * @param mixed $value	属性值
     * @return void
     */
    public function __set($property, $value)
    {
        if (isset($this->$property))
        {
            $this->$property = $value;
        }
    }

    /**
     * 魔术方法, 发起GET/PUT/HEAD/POST/DELETE请求操作
     *
     * @param string $method HTTP请求方法
     * @param array $args 参数列表(url,headers,content,streams)
     * @throws OSS_Exception
     * @return OSS_Response
     */
    public function __call($method, array $args)
    {
        $method = strtoupper($method);
        if (!in_array($method, array('GET', 'PUT', 'HEAD', 'POST', 'DELETE')))
        {
            throw new OSS_Exception('Invalid HTTP method ' . $method);
        }
        if (!isset($args[0]) || !is_string($args[0]))
        {
            throw new OSS_Exception('You must specify the URL to ' . $method);
        }
        $scheme = $this->useSSL ? 'https://' : 'http://';
        $url = $scheme . $this->host . '/' . ltrim($args[0], '/');
        $headers = OSS_UT::trySet($args, 1, array());
        $content = OSS_UT::trySet($args, 2, null);
        $streams = OSS_UT::trySet($args, 3, array());
        if (null !== $content)
        {
            $headers['Content-Length'] = OSS_UT::trySet($headers, 'Content-Length', strlen($content));
            $headers['Content-Md5'] = OSS_UT::hex2Base64(md5($content));
        }
        $strToSign = '';
        $headers['Content-Type'] = OSS_UT::trySet($headers, 'Content-Type', 'application/x-www-form-urlencoded');
        $headers['Host'] = $this->host;
        $headers['Date'] = OSS_UT::trySet($headers, 'Date', gmdate('D, d M Y H:i:s \G\M\T'));
        $headers['Authorization'] = $this->signature($method, $url, $headers, $strToSign);
        if (isset($headers['Sign-URL']))
        {
            return $this->signURL($url, $headers);
        }
        return $this->sendRequest($method, $url, $headers, $content, $streams, $strToSign);
    }

    /**
     * 构造一个带签名的Object URL
     *
     * @access public
     * @param string $url 请求URL
     * @param string $headers 请求头信息
     * @return string
     */
    public function signURL($url, $headers)
    {
        if (is_int($headers['Date']) && $headers['Date'] > 0)
        {
            $expire = $headers['Date'];
            $signature = rawurlencode(end(explode(':', $headers['Authorization'])));
            $conjunction = (false !== strpos($url, '?') ? '&' : '?');
            return $url . $conjunction . "OSSAccessKeyId=$this->accessID&Expires=$expire&Signature=$signature";
        }
        else
        {
            return $url;
        }
    }

    /**
     * 生成签名验证码
     *
     * @param string $method HTTP请求方法
     * @param string $url 请求 URL
     * @param string $headers 请求头信息
     * @param string $strToSign 通过引用返回签名原始字符串
     * @return string
     */
    private function signature($method, $url, array $headers, &$strToSign)
    {
        $datas = array();
        $datas[] = $method;
        $datas[] = OSS_UT::trySet($headers, 'Content-Md5', '');
        $datas[] = OSS_UT::trySet($headers, 'Content-Type', '');
        $datas[] = OSS_UT::trySet($headers, 'Date', gmdate('D, d M Y H:i:s \G\M\T'));
        uksort($headers, 'strnatcasecmp');
        // CanonicalizedOSSHeaders
        foreach ($headers as $key => $value)
        {
            $value = trim($value);
            if ('x-oss-' === substr(strtolower($key), 0, 6))
            {
                $datas[] = strtolower($key) . ':' . $value;
            }
        }
        // CanonicalizedResource
        $attach = '';
        $parts = parse_url($url);
        if (isset($parts['path']))
        {
            // bucket/object
            $attach .= '/' . ltrim(rawurldecode($parts['path']), '/');
        }
        if (isset($parts['query']))
        {
            $params = explode('&', $parts['query']);
            $conjunction = '?';
            // sub resource
            if (false === strpos($params[0], '='))
            {
                $attach .= ($conjunction . $params[0]);
                array_shift($params);
                $conjunction = '&';
            }
            $signableKeys = array('partNumber', 'uploadId');
            // override params
            foreach ($params as $param)
            {
                list($key, $value) = explode('=', $param);
                if (false !== strpos($key, 'reponse-') || in_array($key, $signableKeys))
                {
                    $attach .= ($conjunction . urldecode($key) . '=' . urldecode($value));
                    $conjunction = '&';
                }
            }
        }
        if ('' !== $attach)
        {
            $datas[] = $attach;
        }
        $strToSign = implode("\n", $datas);
        $signature = base64_encode(hash_hmac('sha1', $strToSign, $this->accessKey, true));
        return 'OSS ' . $this->accessID . ':' . $signature;
    }

    /**
     * 发送HTTP请求
     *
     * @param string $method HTTP请求方法
     * @param string $url 请求 URL
     * @param array $headers 请求头信息
     * @param string $content 请求内容
     * @param array $streams 流数据自定义信息[stream,length|seek]
     * @param string $strToSign 签名原始字符串
     * @return string
     */
    private function sendRequest($method, $url, array $headers, $content, array $streams, $strToSign)
    {
        set_time_limit(0);

		if (!function_exists('curl_version'))
		{
			throw new OSS_Exception('Can not find curl extension.');
		}

        $ch = curl_init($url);
        // settings
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_FRESH_CONNECT, false);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
        curl_setopt($ch, CURLOPT_REFERER, $url);
        curl_setopt($ch, CURLOPT_ENCODING, "");
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->useSSL);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->useSSL);
        curl_setopt($ch, CURLOPT_USERAGENT, sprintf('oss-sdk-php (%s) / PHP (%s)', OSS_SDK::VERSION, phpversion()));

        // Proxy
        if (!empty($this->proxy) && isset($this->proxy['host']))
        {
            curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, true);

            $host = $this->proxy['host'];
            if (isset($this->proxy['port']))
            {
                $host .= ':' . $this->proxy['port'];
            }
            curl_setopt($ch, CURLOPT_PROXY, $host);

            if (isset($this->proxy['user'], $this->proxy['pass']))
            {
                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']);
            }
        }

        // Debug mode
        if ($this->debug)
        {
            curl_setopt($ch, CURLOPT_VERBOSE, true);
        }

        // HTTP method
        switch ($method)
        {
            case 'PUT' :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
                if (!empty($streams))
                {
                    $stream = new OSS_Stream($streams, true);
                    curl_setopt($ch, CURLOPT_READFUNCTION, array(&$stream, 'readStreamCallback'));
                    curl_setopt($ch, CURLOPT_INFILESIZE, $stream->getReadLength());
                    curl_setopt($ch, CURLOPT_UPLOAD, true);
                }
                else
                {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
                }
                break;
            case 'POST' :
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
                break;
            case 'HEAD' :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
                curl_setopt($ch, CURLOPT_NOBODY, 1);
                break;
            default :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
                if (!empty($streams))
                {
                    $stream = new OSS_Stream($streams, false);
                    curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(&$stream, 'writeStreamCallback'));
                    curl_setopt($ch, CURLOPT_HEADER, false);
                }
                else if (null !== $content)
                {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
                }
                break;
        }
        // HTTP header
        $requestHeaders = array();
        if (!empty($headers))
        {
            foreach ($headers as $key => $value)
            {
                $requestHeaders[] = trim($key) . ': ' . trim($value);
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders);
        }

        if (false === ($result = curl_exec($ch)))
        {
            throw new OSS_Exception('cURL resource: ' . (string) $ch . '; cURL error: ' . curl_error($ch) . ' (' . curl_errno($ch) . ')');
        }

        $response = new OSS_Response($ch, $result);
        $response->addHeader('x-oss-request-url', $url);
        $response->addHeader('x-oss-redirects', $this->redirects);
        $response->addHeader('x-oss-stringtosign', $strToSign);
        $response->addHeader('x-oss-request-headers', $requestHeaders);

        // Logs message
        $this->logMessage($response);

        if (!$response->status || in_array($response->status, array(400, 500, 503)))
        {
            if ($this->redirects <= $this->maxRetries)
            {
                $delay = (integer) (pow(4, $this->redirects) * 100000);
                usleep($delay);
                $this->redirects++;
                $response = $this->sendRequest($method, $url, $headers, $content, $streams, $strToSign);
            }
        }
        $this->redirects = 0;
        curl_close($ch);

        return $response;
    }

    /**
     * 记录HTTP请求日志
     *
     * @param object $response 返回结果对象
     * @return void
     */
    private function logMessage($response)
    {
        if ('' === $this->logDir || !is_dir($this->logDir))
        {
            return;
        }
        $type = $response->isOk() ? '_ok.log' : '_error.log';
        $file = $this->logDir . '/oss_sdk_php_' . date('Y-m-d') . $type;
        $message = $response->formatTrace() . "\n";
        $message .= "----------String To Sign -------------\n";
        $message .= $response->header['x-oss-stringtosign'] . "\n";
        return @error_log($message, 3, $file);
    }
}

/*
|--------------------------------------------------------------------
| OSS返回结果封装类
|--------------------------------------------------------------------
|
*/

class OSS_Response
{
    /**
     * HTTP返回状态码
     *
     * @var int
     */
    public $status;

    /**
     * 返回Header名值对
     *
     * @var array
     */
    public $header = array();

    /**
     * 返回Body(XML)
     *
     * @var string
     */
    public $body;

    /**
     * 日志信息
     *
     * @var string
     */
    public $traces = array();

    /**
     * 构造函数
     *
     * @param resource $ch cURL连接资源句柄
     * @param string $response 操作结果
     */
    function __construct($ch, $response)
    {
        if (is_resource($ch) && !empty($response))
        {
            $array = explode("\r\n\r\n", $response);
            $body = trim(array_pop($array));
            $header = trim(array_pop($array));
            $code = (integer) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $info = curl_getinfo($ch);

            $this->traces['request_header'] = $info['request_header'];
            $this->traces['response_header'] = $header;
            $this->traces['response_body'] = $body;

            $header = explode("\r\n", $header);
            array_shift($header);
            $assoc = array();
            foreach ($header as $line)
            {
                $arr = explode(': ', $line);
                $assoc[$arr[0]] = isset($arr[1]) ? $arr[1] : '';
            }

            $this->status = $code;
            $this->header = $assoc;
            $this->header['_info'] = $info;
            $this->body = $body;
        }
    }

    /**
     * 验证请求是否被服务器正确处理
     *
     * @return bool
     */
    public function isOK()
    {
        return in_array($this->status, array(200, 201, 204, 206));
    }

    /**
     * 附加返回头信息
     *
     * @param string $key Header关键字
     * @param string $value Header值
     * @return void
     */
    public function addHeader($key, $value)
    {
        $this->header[$key] = $value;
    }

    /**
     * 获取日志信息
     *
     * @param bool $nobody 不包含Body内容
     * @return string
     */
    public function formatTrace($nobody = true)
    {
        $logs = array();
        $logs[] = '----------Trace Stream [' . date('Y-m-d:H:i:s') . ']-------------';
        $logs[] = OSS_UT::trySet($this->traces, 'request_header', '');
        $logs[] = OSS_UT::trySet($this->traces, 'response_header', '');
        if (!$nobody || !$this->isOK())
        {
            $logs[] = "\n" . OSS_UT::trySet($this->traces, 'response_body', '');
        }
        return implode("\n", $logs);
    }
}

/*
|--------------------------------------------------------------------
| OSS流数据操作类
|--------------------------------------------------------------------
|
*/
class OSS_Stream
{
    /**
     * 流数据文件[读/写]
     *
     * @var string
     */
    private $file;

    /**
     * 流数据文件句柄[读/写]
     *
     * @var string
     */
    private $stream;

    /**
     * 待上传的流数据字节数
     *
     * @var int
     */
    private $length;

    /**
     * 设定待上传的文件当前指针位置
     *
     * @var int
     */
    private $seek;

    /**
     * 已上传的流数据字节数
     *
     * @var int
     */
    private $readin = 0;

    /**
     * 构造函数
     *
     * @param array $streams 流数据自定义信息[stream,length|seek]
     * @param bool $forUpload 是否为上传数据
     */
    function __construct(array $streams, $forUpload)
    {
        if ($forUpload)
        {
            $this->setReadStreams($streams);
        }
        else
        {
            $this->setWriteStreams($streams);
        }
    }

    /**
     * 获取待上传的流数据字节数
     *
     * @return int
     */
    public function getReadLength()
    {
        return (integer) $this->length;
    }

    /**
     * 获取待上传的文件当前指针位置
     *
     * @return int
     */
    public function getReadSeek()
    {
        return (integer) $this->seek;
    }

    /**
     * 设置流式上传文件自定义信息
     *
     * @param array $streams 流数据自定义信息[stream,length|seek]
     * @return void
     */
    private function setReadStreams(array $streams)
    {
        if (is_resource($streams['stream']))
        {
            $this->stream = $streams['stream'];
        }
        else
        {
            $this->file = $streams['stream'];
            if (false === ($this->stream = fopen($streams['stream'], 'r')))
            {
                throw new OSS_Exception('failed to open file:' . $streams['stream']);
            }
        }
        if (!isset($streams['length']) || $streams['length'] < 0)
        {
            $stats = fstat($this->stream);
            if ($stats && $stats['size'] >= 0)
            {
                $position = ftell($this->stream);
                if (false !== $position && $position >= 0)
                {
                    $streams['length'] = $stats['size'] - $position;
                }
            }
        }
        if (isset($streams['seek']) && $streams['seek'] >= 0)
        {
            $this->seek = $streams['seek'];
        }
        $this->length = $streams['length'];
    }

    /**
     * 设置流式下载文件自定义信息
     *
     * @param array $streams 流数据自定义信息[stream]
     * @return void
     */
    private function setWriteStreams(array $streams)
    {
        if (is_resource($streams['stream']))
        {
            $this->stream = $streams['stream'];
        }
        else
        {
            $this->file = $streams['stream'];
            if (false === ($this->stream = fopen($streams['stream'], 'w')))
            {
                throw new OSS_Exception('failed to open file:' . $streams['stream']);
            }
        }
    }

    /**
     * 读取文件内容用于添加到请求当中（上传数据）
     *
     * @param resource $ch cURL连接资源句柄
     * @param resource $fh 流数据文件句柄
     * @param int $size 读取字节数
     * @return string
     */
    public function readStreamCallback($ch, $fh, $size)
    {
        if ($this->readin >= $this->length)
        {
            return '';
        }

        if ($this->readin == 0 && isset($this->seek) && $this->seek !== ftell($this->stream))
        {
            if (fseek($this->stream, $this->seek) !== 0)
            {
                throw new OSS_Exception('Failed to seek position for reading stream');
            }
        }

        $read = fread($this->stream, min($this->length - $this->readin, $size));
        $this->readin += strlen($read);

        $data = $read === false ? '' : $read;
        return $data;
    }

    /**
     * 把接收到的数据写入到指定文件中（下载数据）
     *
     * @param resource $ch cURL连接资源句柄
     * @param string $data 流数据
     * @return int
     */
    public function writeStreamCallback($ch, $data)
    {
        $size = strlen($data);
        $total = 0;
        $last = 0;

        while ($total < $size)
        {
            $last = fwrite($this->stream, substr($data, $total));
            if (false === $last)
            {
                return $total;
            }
            $total += $last;
        }
        return $total;
    }

    /**
     * 析构函数，用于关闭内部创建的文件句柄
     *
     * @return void
     */
    public function __destruct()
    {
        if (isset($this->file, $this->stream))
        {
            @fclose($this->stream);
        }
    }
}

/*
|--------------------------------------------------------------------
| OSS工具类
|--------------------------------------------------------------------
|
*/
class OSS_UT
{

    /**
     * 获取数组元素，键名或索引未定义则返回指定的默认值
     *
     * @param array $array 数组
     * @param string $key 键名或索引
     * @param mixed $default 指定的默认值
     * @return mixed
     */
    public static function trySet(array &$array, $key, $default = null)
    {
        if (!isset($array[$key]))
        {
            $array[$key] = $default;
        }
        return $array[$key];
    }

    /**
     * 检验Bucket名称是否合法，Bucket的命名规范：
     * 1. 只能包括小写字母，数字
     * 2. 必须以小写字母或者数字开头
     * 3. 长度必须在3-63字节之间
     *
     * @param string $bucket
     * @throws OSS_Exception
     * @return void
     */
    public static function parseBucket(&$bucket)
    {
        if (!preg_match('/^[a-z0-9][a-z0-9]{2,62}$/', $bucket))
        {
            throw new OSS_Exception('Invalid bucket name: ' . $bucket);
        }
    }

    /**
     * 检验Object名称是否合法，Object命名规范:
     * 1. 规则长度必须在1-1023字节之间
     * 2. 使用UTF-8编码
     *
     * @param string $object
     * @throws OSS_Exception
     * @return void
     */
    public static function parseObject(&$object)
    {
        if (!preg_match('/^.{1,1023}$/', $object))
        {
            throw new OSS_Exception('Invalid object name: ' . $object);
        }
        $object = OSS_UT::tryGBK2UTF($object);
        $object = str_replace('\\', '/', $object);
        $object = str_replace('%2F', '/', rawurlencode($object));
    }

    /**
     * 检验定义的分块大小是否符合要求，通过引用返回正确的分块大小
     *
     * @param int $partSize 定义的分块大小
     * @return void
     */
    public static function parsePartSize(&$partSize)
    {
        if ((integer) $partSize <= 5242880)
        {
            $partSize = 5242880; //5M
        }
        elseif ((integer) $partSize > 524288000)
        {
            $partSize = 524288000; //500M
        }
        else
        {
            $partSize = 52428800; //50M
        }
    }

    /**
     * 把UTF-8编码的中文字符串转换成GBK编码（Windows下的中文文件名默认为GBK编码）
     *
     * @param string $str 字符串
     * @return string
     */
    public static function tryUTF2GBKforCN($str)
    {
        if (preg_match('/[\x80-\xff]./', $str) && OSS_UT::isUTF8($str))
        {
            $str = iconv('UTF-8', 'GBK', $str);
        }
        return $str;
    }

    /**
     * 把GBK编码字符串转换成UTF-8编码（OSS中的文件名均是UTF-8编码）
     *
     * @param string $str 字符串
     * @return string
     */
    public static function tryGBK2UTF($str)
    {
        if (OSS_UT::isGB2312($str))
        {
            $str = iconv('GB2312', 'UTF-8', $str);
        }
        elseif (OSS_UT::isGBK($str))
        {
            $str = iconv('GBK', 'UTF-8', $str);
        }
        return $str;
    }

    /**
     * 检测字符串是否为UTF-8编码
     *
     * @param string $str
     * @return bool
     */
    public static function isUTF8($str)
    {
        return preg_match('%^(?:
          [\x09\x0A\x0D\x20-\x7E]            # ASCII
        | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
        |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
        |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
        |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
        | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
        |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
    	)*$%xs', $str);
    }

    /**
     * 检测字符串是否为GB2312编码
     *
     * @param string $str
     * @return bool |null
     */
    public static function isGB2312($str)
    {
        for ($i = 0; $i < strlen($str); $i++)
        {
            $v = ord($str[$i]);
            if ($v > 127)
            {
                if (($v >= 228) && ($v <= 233))
                {
                    if (($i + 2) >= (strlen($str) - 1))
                    {
                        return true;
                    }
                    $v1 = ord($str[$i + 1]);
                    $v2 = ord($str[$i + 2]);
                    if (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191))
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }
        }
    }

    /**
     * 检测字符串是否为GBK编码
     *
     * @param string $str
     * @return bool
     */
    public static function isGBK($str)
    {
        $isGB2312 = OSS_UT::isGB2312($str);

        return null !== $isGB2312 ? $isGB2312 : true;
    }

    /**
     * 使用 base64对十六进制数据进行编码
     *
     * @param string $data 十六进制数据
     * @return string
     */
    public static function hex2Base64($data)
    {
        $result = '';
        for ($i = 0; $i < strlen($data); $i += 2)
        {
            $result .= chr(hexdec(substr($data, $i, 2)));
        }
        return base64_encode($result);
    }

    /**
     * 读取目录中的文件列表
     * @param string $dir 目录名
     * @param boolean $recursive 是否递归遍历子目录
     * @param string $exclude 过滤的文件/目录
     * @return array
     */
    public static function readDir($dir, $exclude = ".|..|.svn", $recursive = false)
    {
        static $files = array();

        $excludes = explode("|", $exclude);
        if (false !== ($handle = opendir($dir)))
        {
            while (false !== ($file = readdir($handle)))
            {
                if (!in_array(strtolower($file), $excludes))
                {
                    $path = $dir . '/' . $file;
                    if (is_dir($path))
                    {
                        $recursive && OSS_UT::readDir($path, $exclude, $recursive);
                    }
                    else
                    {
                        $files[] = array
                        (
                            'path' => $path,
                            'file' => $file
                        );
                    }
                }
            }
            closedir($handle);
        }
        return $files;
    }

    /**
     * 获取文件MIME类型
     *
     * @param string $filename 文件名称
     * @return string
     */
    public static function mimeType($filename)
    {
        $types = array
        (
            'apk'    => 'application/vnd.android.package-archive',
            '3gp'    => 'video/3gpp',
            'ai'     => 'application/postscript',
            'aif'    => 'audio/x-aiff',
            'aifc'   => 'audio/x-aiff',
            'aiff'   => 'audio/x-aiff',
            'asc'    => 'text/plain',
            'atom'   => 'application/atom+xml',
            'au'     => 'audio/basic',
            'avi'    => 'video/x-msvideo',
            'bcpio'  => 'application/x-bcpio',
            'bin'    => 'application/octet-stream',
            'bmp'    => 'image/bmp',
            'cdf'    => 'application/x-netcdf',
            'cgm'    => 'image/cgm',
            'class'  => 'application/octet-stream',
            'cpio'   => 'application/x-cpio',
            'cpt'    => 'application/mac-compactpro',
            'csh'    => 'application/x-csh',
            'css'    => 'text/css',
            'dcr'    => 'application/x-director',
            'dif'    => 'video/x-dv',
            'dir'    => 'application/x-director',
            'djv'    => 'image/vnd.djvu',
            'djvu'   => 'image/vnd.djvu',
            'dll'    => 'application/octet-stream',
            'dmg'    => 'application/octet-stream',
            'dms'    => 'application/octet-stream',
            'doc'    => 'application/msword',
            'dtd'    => 'application/xml-dtd',
            'dv'     => 'video/x-dv',
            'dvi'    => 'application/x-dvi',
            'dxr'    => 'application/x-director',
            'eps'    => 'application/postscript',
            'etx'    => 'text/x-setext',
            'exe'    => 'application/octet-stream',
            'ez'     => 'application/andrew-inset',
            'flv'    => 'video/x-flv',
            'gif'    => 'image/gif',
            'gram'   => 'application/srgs',
            'grxml'  => 'application/srgs+xml',
            'gtar'   => 'application/x-gtar',
            'gz'     => 'application/x-gzip',
            'hdf'    => 'application/x-hdf',
            'hqx'    => 'application/mac-binhex40',
            'htm'    => 'text/html',
            'html'   => 'text/html',
            'ice'    => 'x-conference/x-cooltalk',
            'ico'    => 'image/x-icon',
            'ics'    => 'text/calendar',
            'ief'    => 'image/ief',
            'ifb'    => 'text/calendar',
            'iges'   => 'model/iges',
            'igs'    => 'model/iges',
            'jnlp'   => 'application/x-java-jnlp-file',
            'jp2'    => 'image/jp2',
            'jpe'    => 'image/jpeg',
            'jpeg'   => 'image/jpeg',
            'jpg'    => 'image/jpeg',
            'js'     => 'application/x-javascript',
            'kar'    => 'audio/midi',
            'latex'  => 'application/x-latex',
            'lha'    => 'application/octet-stream',
            'lzh'    => 'application/octet-stream',
            'm3u'    => 'audio/x-mpegurl',
            'm4a'    => 'audio/mp4a-latm',
            'm4p'    => 'audio/mp4a-latm',
            'm4u'    => 'video/vnd.mpegurl',
            'm4v'    => 'video/x-m4v',
            'mac'    => 'image/x-macpaint',
            'man'    => 'application/x-troff-man',
            'mathml' => 'application/mathml+xml',
            'me'     => 'application/x-troff-me',
            'mesh'   => 'model/mesh',
            'mid'    => 'audio/midi',
            'midi'   => 'audio/midi',
            'mif'    => 'application/vnd.mif',
            'mov'    => 'video/quicktime',
            'movie'  => 'video/x-sgi-movie',
            'mp2'    => 'audio/mpeg',
            'mp3'    => 'audio/mpeg',
            'mp4'    => 'video/mp4',
            'mpe'    => 'video/mpeg',
            'mpeg'   => 'video/mpeg',
            'mpg'    => 'video/mpeg',
            'mpga'   => 'audio/mpeg',
            'ms'     => 'application/x-troff-ms',
            'msh'    => 'model/mesh',
            'mxu'    => 'video/vnd.mpegurl',
            'nc'     => 'application/x-netcdf',
            'oda'    => 'application/oda',
            'ogg'    => 'application/ogg',
            'ogv'    => 'video/ogv',
            'pbm'    => 'image/x-portable-bitmap',
            'pct'    => 'image/pict',
            'pdb'    => 'chemical/x-pdb',
            'pdf'    => 'application/pdf',
            'pgm'    => 'image/x-portable-graymap',
            'pgn'    => 'application/x-chess-pgn',
            'pic'    => 'image/pict',
            'pict'   => 'image/pict',
            'png'    => 'image/png',
            'pnm'    => 'image/x-portable-anymap',
            'pnt'    => 'image/x-macpaint',
            'pntg'   => 'image/x-macpaint',
            'ppm'    => 'image/x-portable-pixmap',
            'ppt'    => 'application/vnd.ms-powerpoint',
            'ps'     => 'application/postscript',
            'qt'     => 'video/quicktime',
            'qti'    => 'image/x-quicktime',
            'qtif'   => 'image/x-quicktime',
            'ra'     => 'audio/x-pn-realaudio',
            'ram'    => 'audio/x-pn-realaudio',
            'ras'    => 'image/x-cmu-raster',
            'rdf'    => 'application/rdf+xml',
            'rgb'    => 'image/x-rgb',
            'rm'     => 'application/vnd.rn-realmedia',
            'roff'   => 'application/x-troff',
            'rtf'    => 'text/rtf',
            'rtx'    => 'text/richtext',
            'sgm'    => 'text/sgml',
            'sgml'   => 'text/sgml',
            'sh'     => 'application/x-sh',
            'shar'   => 'application/x-shar',
            'silo'   => 'model/mesh',
            'sit'    => 'application/x-stuffit',
            'skd'    => 'application/x-koan',
            'skm'    => 'application/x-koan',
            'skp'    => 'application/x-koan',
            'skt'    => 'application/x-koan',
            'smi'    => 'application/smil',
            'smil'   => 'application/smil',
            'snd'    => 'audio/basic',
            'so'     => 'application/octet-stream',
            'spl'    => 'application/x-futuresplash',
            'src'    => 'application/x-wais-source',
            'sv4cpio'=> 'application/x-sv4cpio',
            'sv4crc' => 'application/x-sv4crc',
            'svg'    => 'image/svg+xml',
            'swf'    => 'application/x-shockwave-flash',
            't'      => 'application/x-troff',
            'tar'    => 'application/x-tar',
            'tcl'    => 'application/x-tcl',
            'tex'    => 'application/x-tex',
            'texi'   => 'application/x-texinfo',
            'texinfo'=> 'application/x-texinfo',
            'tif'    => 'image/tiff',
            'tiff'   => 'image/tiff',
            'tr'     => 'application/x-troff',
            'tsv'    => 'text/tab-separated-values',
            'txt'    => 'text/plain',
            'ustar'  => 'application/x-ustar',
            'vcd'    => 'application/x-cdlink',
            'vrml'   => 'model/vrml',
            'vxml'   => 'application/voicexml+xml',
            'wav'    => 'audio/x-wav',
            'wbmp'   => 'image/vnd.wap.wbmp',
            'wbxml'  => 'application/vnd.wap.wbxml',
            'webm'   => 'video/webm',
            'wml'    => 'text/vnd.wap.wml',
            'wmlc'   => 'application/vnd.wap.wmlc',
            'wmls'   => 'text/vnd.wap.wmlscript',
            'wmlsc'  => 'application/vnd.wap.wmlscriptc',
            'wmv'    => 'video/x-ms-wmv',
            'wrl'    => 'model/vrml',
            'xbm'    => 'image/x-xbitmap',
            'xht'    => 'application/xhtml+xml',
            'xhtml'  => 'application/xhtml+xml',
            'xls'    => 'application/vnd.ms-excel',
            'xml'    => 'application/xml',
            'xpm'    => 'image/x-xpixmap',
            'xsl'    => 'application/xml',
            'xslt'   => 'application/xslt+xml',
            'xul'    => 'application/vnd.mozilla.xul+xml',
            'xwd'    => 'image/x-xwindowdump',
            'xyz'    => 'chemical/x-xyz',
            'zip'    => 'application/zip'
        );
        $ext = false !== strpos($filename, '.') ? strtolower(end(explode('.', $filename))) : '';
        return isset($types[$ext]) ? $types[$ext] : 'application/octet-stream';
    }
}

/*
|--------------------------------------------------------------------
| OSS自定义异常类
|--------------------------------------------------------------------
|
*/
class OSS_Exception extends Exception
{
}

?>